Start by loading necessary packages, including sqldf for EDA.
library(here)
## here() starts at C:/Users/wjrea/RProjects/constituencies
library(readr)
library(sf)
## Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.3.1; sf_use_s2() is TRUE
library(leaflet)
library(tmap)
## Breaking News: tmap 3.x is retiring. Please test v4, e.g. with
## remotes::install_github('r-tmap/tmap')
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(rvest)
##
## Attaching package: 'rvest'
## The following object is masked from 'package:readr':
##
## guess_encoding
library(sqldf)
## Loading required package: gsubfn
## Loading required package: proto
## Loading required package: RSQLite
Read in constituency geometries for 2019 and 2024 (following boundary changes), downloaded from https://geoportal.statistics.gov.uk/.
uk_seats_2024 <- st_read(here("data",
paste0("Westminster_Parliamentary_Constituencies",
"_July_2024_Boundaries_UK_BGC_",
"-9160795013504052944.gpkg")))
## Reading layer `PCON_MAY_2024_UK_BGC' from data source
## `C:\Users\wjrea\RProjects\constituencies\data\Westminster_Parliamentary_Constituencies_July_2024_Boundaries_UK_BGC_-9160795013504052944.gpkg'
## using driver `GPKG'
## Simple feature collection with 650 features and 8 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: -116.1487 ymin: 5352.6 xmax: 655653.8 ymax: 1220302
## Projected CRS: OSGB36 / British National Grid
uk_seats_2019 <- st_read(here("data",
paste0("WPC_Dec_2019_GCB_UK_2022_",
"-3630186270451075951.gpkg")))
## Reading layer `WPC_Dec_2019_GCB_UK' from data source
## `C:\Users\wjrea\RProjects\constituencies\data\WPC_Dec_2019_GCB_UK_2022_-3630186270451075951.gpkg'
## using driver `GPKG'
## Simple feature collection with 650 features and 7 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: -71.5668 ymin: 5350.361 xmax: 655653.8 ymax: 1220302
## Projected CRS: OSGB36 / British National Grid
Read in 2019 election results, downloaded from https://www.theyworkforyou.com/mps/, then join to constituency geometries.
mp_tib <- read_csv(here("data", "mps.csv"), show_col_types = FALSE)
uk_seats_2019[which(uk_seats_2019$pcon19nm == "Ynys Mon"), ]$pcon19nm <- "Ynys Môn"
uk_seats_2019a <- left_join(uk_seats_2019, mp_tib, join_by(pcon19nm == Constituency))
Read 2024 election results page, from the BBC.
ge_2024_results_page <- read_html("https://www.bbc.co.uk/news/election/2024/uk/constituencies")
containers <- ge_2024_results_page %>% html_nodes("div.ewuptu24")
no_seats <- length(containers)
constituencies <- rep(NA, length(containers))
winners <- rep(NA, length(containers))
for (i in 1:no_seats) {
constituencies[i] <- containers[i] %>% html_nodes("a.ewuptu21") %>% html_text()
tryCatch(
{
winners[i] <- containers[i] %>% html_nodes("div.ewuptu20") %>% html_text()
},
error = function(e) {
cat(sprintf("Error in %s:", constituencies[i]), e$message, "\n")
}
)
}
results_2024_df <- data.frame(cbind(constituencies, winners))
colnames(results_2024_df) <- c("constituency", "party")
# This was the last one in
results_2024_df[which(results_2024_df$constituency == "Inverness, Skye and West Ross-shire"),
]$party <- "Liberal Democrat gain from Scottish National Party"
results_2024_df$party <- gsub(" *(gain|hold|win)( .*|)$", "", results_2024_df$party)
# Ensure constituency names match before joining dataframes
results_2024_df[which(results_2024_df$constituency == "Bridlington and the Wolds"),
]$constituency <- "Bridlington and The Wolds"
results_2024_df[which(results_2024_df$constituency == "Hull East"),
]$constituency <- "Kingston upon Hull East"
results_2024_df[which(results_2024_df$constituency == "Hull North & Cottingham"),
]$constituency <- "Kingston upon Hull North and Cottingham"
results_2024_df[which(results_2024_df$constituency == "Hull West & Haltemprice"),
]$constituency <- "Kingston upon Hull West and Haltemprice"
results_2024_df[which(results_2024_df$constituency == "South Holland and the Deepings"),
]$constituency <- "South Holland and The Deepings"
Modify party names in 2024 results where necessary, for colour list below.
results_2024_df[which(results_2024_df$party == "Alliance Party"),
]$party <- "Alliance"
results_2024_df[which(results_2024_df$party == "Sinn Fein"),
]$party <- "Sinn Féin"
results_2024_df[which(results_2024_df$party == "Social Democratic & Labour Party"),
]$party <- "Social Democratic and Labour Party"
results_2024_df[which(results_2024_df$party == "Democratic Unionist Party"),
]$party <- "DUP"
results_2024_df[which(results_2024_df$party == "Speaker of the House of Commons"),
]$party <- "Speaker"
Join geometry and results dataframes for 2024, on constituency name.
uk_seats_2024a <- left_join(uk_seats_2024, results_2024_df, join_by(PCON24NM == constituency))
Set up colours.
party_colours <- c("Conservative" = "#0087DC",
"Labour" = "#E4003B",
"Liberal Democrat" = "#FAA61A",
"Scottish National Party" = "#FDF38E",
"Green" = "#02A95B",
"Labour/Co-operative" = "#E4003B",
"Reform UK" = "#12B6CF",
"Sinn Féin" = "#326760",
"Alliance" = "#F6CB2F",
"DUP" = "#D46A4C",
"Plaid Cymru" = "#005B54",
"Social Democratic and Labour Party" = "#2AA82C",
"Alba" = "#005EB8",
"Workers Party" = "#780021",
"Speaker" = "black",
"Independent" = "#DCDCDC",
"Reclaim" = "#C03F31",
"Traditional Unionist Voice" = "#0C3A6A",
"Ulster Unionist Party" = "#48A5EE")
all_values_2019 <- unique(uk_seats_2019a$Party)
all_values_2024 <- unique(uk_seats_2024a$party)
# Assign default colours for unspecified values
default_palette_2019 <- tmaptools::get_brewer_pal("Set1", n = length(all_values_2019), contrast = c(0.2, 0.8))
## Warning in tmaptools::get_brewer_pal("Set1", n = length(all_values_2019), :
## contrast not used in qualitative color palettes
default_palette_2024 <- tmaptools::get_brewer_pal("Set1", n = length(all_values_2024), contrast = c(0.2, 0.8))
## Warning in tmaptools::get_brewer_pal("Set1", n = length(all_values_2024), :
## contrast not used in qualitative color palettes
# Combine custom colours with defaults
full_palette_2019 <- setNames(default_palette_2019, all_values_2019)
full_palette_2019[names(party_colours)] <- party_colours
full_palette_2024 <- setNames(default_palette_2024, all_values_2024)
full_palette_2024[names(party_colours)] <- party_colours
View 2019 interactive constituency map.
interactive_2019_map <- tm_shape(uk_seats_2019a) +
tm_polygons(col = "Party", palette = full_palette_2019,
title = "Constituencies pre 2024 Election",
id = "pcon19nm",
popup.vars = c("Party" = "Party"))
tmap_leaflet(interactive_2019_map)